//MAGISKLOOPER v1.3.3
//BY MATHIAS BREDHOLT 2011
//FEEL FREE TO USE AS YOU LIKE

MagiskLooper {
	var <w,<c,<t,<s;
	var <children;
	var focus,<recording,allMuted,octave,<latency;
	
	*new {
		^super.new.init();
	}
	
	init {
		var recBtn;
		allMuted = false;
		octave = 1;
		children = List.new;
		recording = false;
		latency = 1300;
		
		s = Server.default;
		t = TempoClock.default;
		
		if (s.serverRunning) {
			SynthDef(\magiskLooper,{|in,out,bufnum,rec=0,play=1,oct=1,rev=1,latency = 1300,length=44100|
				var inSig,playPos,recPos,read,recRead;
				
				inSig = SoundIn.ar(in)*rec;
				recPos = Phasor.ar(0,BufRateScale.kr(bufnum)*oct*rev,0,length);
				recRead = BufRd.ar(1,bufnum,recPos);
				BufWr.ar(inSig+recRead,bufnum,recPos);
				
				playPos = Wrap.ar((recPos + latency),0,length);
				read = BufRd.ar(1,bufnum,playPos);
				Out.ar(out,read.dup*EnvGen.kr(Env.adsr(0.05,1,1,0.1),play));
			}).add;
			
			w = Window.new("MagiskLooper",Rect(500,500,400,140),false)
			.front
			.onClose_({
				children.size.do ({|i|
					children[i].buffer.free;
					children[i].synth.free;
				});
			});
			
			w.view.keyUpAction = {|v,char,mod,uni,key|
				uni.switch (
					32, {
						recBtn.valueAction = (recBtn.value + 1).fold2(1)
	//			13, {
	//				if (mod==131330,{
	//					if (allMuted,{
	//						playBtns.size.do({|i|
	//							playBtns[i].valueAction = 1;
	//							allMuted = false;
	//						});
	//					},{
	//						playBtns.size.do({|i|
	//							playBtns[i].valueAction = 0;
	//							allMuted = true;
	//						});
	//					})
	//				},{
	//					if (playBtns[channel].value == 1,{
	//						playBtns[channel].valueAction = 0;
	//					},{
	//						playBtns[channel].valueAction = 1;
	//					})
	//				});
	//			},
	//			127, {
	//				if (mod == 131330,{
	//					bufferList.size.do({|i|
	//						bufferList[i].zero;
	//						playBtns[i].valueAction = 1;
	//					});
	//					allMuted = false;
	//				},{
	//					bufferList[channel].zero;
	//				});
	//			},
	//			36, { this.newChannel },
	//			49, {
	//				if (chBtns.size>0,{
	//					this.setChannel(0);
	//				});
	//			},
	//			50, {
	//				if (chBtns.size>1,{
	//					this.setChannel(1);
	//				});
	//			},
	//			51, {
	//				if (chBtns.size>2,{
	//					this.setChannel(2);
	//				});
	//			},
	//			52, {
	//				if (chBtns.size>3,{
	//					this.setChannel(3);
	//				});
	//			},
	//			53, {
	//				if (chBtns.size>4,{
	//					this.setChannel(4);
	//				});
	//			},
	//			54, {
	//				if (chBtns.size>5,{
	//					this.setChannel(5);
	//				});
	//			},
	//			55, {
	//				if (chBtns.size>6,{
	//					this.setChannel(6);
	//				});
	//			},
	//			56, {
	//				if (chBtns.size>7,{
	//					this.setChannel(7);
	//				});
				}
				);
			};
	
			c = CompositeView.new(w,Rect(0,0,w.bounds.width,w.bounds.height))
			.background_(Color.rand);
			c.addFlowLayout(10@10,5@5);
	
			Button.new(c,60@20)
			.canFocus_(false)
			.states_([["New",Color.black,Color.rand]])
			.action_({
				children.add(MagiskLooperChannel.new(this));
			});
			
			NumberBox.new(c,30@20)
			.background_(Color.rand)
			.clipLo_(40)
			.clipHi_(255)
			.value_(t.tempo*60)
			.action_({|v|
				t.tempo = v.value/60;
				this.updateChildren;
			})
			.keyDownAction_({|v|
				v.focus(false)
			});
			
			NumberBox.new(c,30@20)
			.background_(Color.rand)
			.clipLo_(2)
			.clipHi_(32)
			.value_(t.beatsPerBar)
			.action_({|v|
				var value=v.value;
				t.schedAbs(t.nextBar, {t.beatsPerBar_(value)});
			})
			.keyDownAction_({|v|
				v.focus(false)
			});
			
			Button.new(c,60@20)
			.canFocus_(false)
			.states_([
				["Oct +2",Color.black,Color.rand],
				["Oct +1",Color.black,Color.rand],
				["Oct 0",Color.black,Color.rand],
				["Oct Ð1",Color.black,Color.rand],
				["Oct Ð2",Color.black,Color.rand]
			])
			.value_(2)
			.action_({|v|
				v.value.switch (
				0,{
					children.size.do ({|i|
						children[i].synth.set(\oct,4);
					});
					octave = 4;
				},
				1,{
					children.size.do ({|i|
						children[i].synth.set(\oct,2);
					});
					octave = 2;
				},
				2,{
					children.size.do ({|i|
						children[i].synth.set(\oct,1);
					});
					octave = 1;
				},
				3,{
					children.size.do ({|i|
						children[i].synth.set(\oct,0.5);
					});
					octave = 0.5;
				},
				4,{
					children.size.do ({|i|
						children[i].synth.set(\oct,0.25);
					});
					octave = 0.25;
				})
			});
			
			Button.new(c,60@20)
			.canFocus_(false)
			.states_([["Reverse",Color.black,Color.rand],["Reverse",Color.black,Color.yellow]])
			.action_({|v|
				if (v.value == 1,{
					children.size.do ({|i|
						children[i].synth.set(\rev,-1);
					});
				},{
					children.size.do ({|i|
						children[i].synth.set(\rev,1);
					});
				})
			});
			
			recBtn = Button.new(c,60@20)
			.canFocus_(false)
			.states_([["Rec",Color.black,Color.rand],["Rec",Color.black,Color.red]])
			.action_({|v|
				if (v.value == 1) {
					children[focus].record;
					recording = true;
				} {
					children[focus].unrecord;
					recording = false;
				}
			});
			
			NumberBox.new(c,40@20)
			.background_(Color.rand)
			.clipLo_(0)
			.clipHi_(2000)
			.value_(latency)
			.action_({|v|
				latency = v.value;
				this.updateChildren;
			})
			.keyDownAction_({|v|
				v.focus(false)
			});
			
			c.decorator.nextLine;
		
			4.do ({		
				{ children.add(MagiskLooperChannel.new(this,0,0,1)) }.defer(0.2)
			});
			
			{ this.setFocus(0) }.defer(1);
		} {
			"BOOT SERVER BEFORE RUNNING!".postln;
		}
	}
	
	setFocus {|id|
		children.size.do ({|i|
			if (i == id) {
				children[i].select;
			} {
				children[i].deselect;
			}
		});
		focus = id;
		w.refresh;
	}
	
	updateChildren {
		children.size.do ({|i|
			children[i].update;
		})
	}
	
//	setChannel {|newChannel|
//		chBtns[channel].states = [[(channel+1).asString]];
//		children[channel].set(\rec,0);
//		
//		channel = newChannel;
//		
//		children[channel].set(\rec,1);
//		chBtns[channel].states = [[(channel+1).asString,Color.white,Color.grey]];
//		
//		w.refresh();
//	}
}

MagiskLooperChannel {
	var parent,in,out,beats;
	var <id;
	var <synth,<buffer;
	var focusBtn,muteBtn;
	
	*new {|parent,in = 0,out = 0,beats = 4|
		^super.newCopyArgs(parent,in,out,beats).init;
	}
	
	init {
		id = parent.children.size;
				
		buffer = Buffer.alloc(parent.s,parent.s.sampleRate*30,1);
		
		synth = Synth(\magiskLooper,[\in,in,\out,out,\bufnum,buffer,\length,parent.t.tempo.reciprocal*beats*parent.s.sampleRate,\latency,parent.latency],addAction:\addBefore);
		
		this.prDrawViews;
	}
	
	select {
		focusBtn.states = [[(id+1).asString,Color.white,Color.grey]];
		if (parent.recording) { synth.set(\rec,1) };
	}
	
	deselect {
		focusBtn.states = [[(id+1).asString,Color.black,Color.rand]];
		synth.set(\rec,0);
	}
	
	mute {
		synth.set(\play,0);
	}
	
	unmute {
		synth.set(\play,1);
	}
	
	record {
		synth.set(\rec,1);
	}
	
	unrecord {
		synth.set(\rec,0);
	}
	
	update {
		var length = parent.t.tempo.reciprocal*beats*parent.s.sampleRate;
		
		synth.set(\in,in,\out,out,\length,length,\latency,parent.latency);
	}
	
	prDrawViews {
		var w,c;
		w = parent.w;
		c = parent.c;
		
		c.decorator.nextLine;
		w.bounds = Rect(w.bounds.left,w.bounds.top-25,w.bounds.width,(w.bounds.height+25).max(135));
		c.bounds = Rect(0,0,w.bounds.width,w.bounds.height+25);
		
		focusBtn = Button.new(c,42@20)
		.states_([[(id+1).asString,Color.black,Color.rand]])
		.canFocus_(false)
		.action_({ parent.setFocus(id) });
		
		PopUpMenu.new(c,40@20)
		.background_(Color.rand)
		.canFocus_(false)
		.items_(Array.fill(21,{|n|n.asString}))
		.value_(in)
		.action_({|v|
			synth.set(\in,v.items[v.value].asInteger);
			in = v.items[v.value].asInteger;
		});
		
		PopUpMenu.new(c,40@20)
		.background_(Color.rand)
		.canFocus_(false)
		.items_(Array.fill(21,{|n|(n+n).asString}))
		.value_(out)
		.action_({|v|
			synth.set(\out,v.items[v.value].asInteger);
			out = v.items[v.value].asInteger;
		});
		
		NumberBox.new(c,40@20)
		.background_(Color.rand)
		.clipLo_(1)
		.value_(beats)
		.action_({|v|
			beats = v.value.asInteger;
			synth.set(\length,parent.t.tempo.reciprocal*beats*parent.s.sampleRate);
		})
		.keyDownAction_({|v|
			v.focus(false)
		});
		
		muteBtn = Button.new(c,60@20)
		.states_([["Mute",Color.black,Color.rand],["Mute",Color.black,Color.cyan]])
		.canFocus_(false)
		.action_({|v|
			if (v.value == 1) { this.mute } { this.unmute }
		});
		
		Button.new(c,60@20)
		.canFocus_(false)
		.states_([["Clear",Color.black,Color.rand]])
		.action_({ 
			buffer.zero;
		});
		
		Button.new(c,60@20)
		.canFocus_(false)
		.states_([["Save",Color.black,Color.rand]])
		.action_({
			CocoaDialog.savePanel({|path|
				buffer.write(path++".aiff","AIFF","int16",parent.t.tempo.reciprocal*beats*parent.s.sampleRate);
			})
		});
	}
}